from typing import Optional
from pydantic import BaseModel

from langchain_core.runnables import RunnableConfig

from .base import BaseNodes, tool_node
from ...models.graph import GraphState
from ...utils.str import unescape_html, replace_file_search_fix


class ReadFileSchema(BaseModel):
    path: str


class WriteFileSchema(BaseModel):
    path: str
    content: str


class ListFilesSchema(BaseModel):
    path: str
    recursive: Optional[bool] = False


class SearchFilesSchema(BaseModel):
    path: str
    regex: str
    file_pattern: Optional[str] = None


class ReplaceInFileSchema(BaseModel):
    path: str
    diff: str


class DownloadResourceSchema(BaseModel):
    resource_name: str
    resource_description: str
    path: str


class FileNodes(BaseNodes):

    @tool_node(args_schema=ReadFileSchema)
    def read_file(self, state: GraphState, *, config: Optional[RunnableConfig] = None):
        """
        读取文件内容。用于分析代码、查看文本或提取配置信息。
        **`read_file`工具仅支持文本文件读取，严禁用于以下文件类型：**
        - **多媒体文件**：图片(.jpg/.png/.gif/.svg等)、音频(.mp3/.wav/.flac等)、视频(.mp4/.avi/.mov等)
        - **二进制文件**：可执行文件(.exe/.bin)、压缩包(.zip/.rar/.tar.gz等)、数据库文件(.db/.sqlite等)
        - **办公文档**：Word(.doc/.docx)、Excel(.xls/.xlsx)、PowerPoint(.ppt/.pptx)、PDF(.pdf)等
        - **特殊格式**：字体文件(.ttf/.otf)、CAD文件(.dwg/.dxf)、设计文件(.psd/.ai)等
        参数:
        - path: (必填) 文件路径(相对当前目录)
        用法:
        <read_file>
        <path>src/index.js</path>
        </read_file>
        """
        return self.wait_human_feedback()

    @tool_node(args_schema=WriteFileSchema, args_parser={"content": unescape_html})
    def write_to_file(self, state: GraphState, *, config: Optional[RunnableConfig] = None):
        """
        写入文件内容。创建新文件或覆盖现有文件，自动创建目录结构。
        ⚠️ 限制：仅支持文本内容，不支持多媒体文件。
        参数:
        - path: (必填) 文件路径(相对当前目录)
        - content: (必填) 完整文件内容，不得截断或省略
        用法:
        <write_to_file>
        <path>src/config.json</path>
        <content>{"key": "value"}</content>
        </write_to_file>
        """
        return self.wait_human_feedback()

    @tool_node(args_schema=ListFilesSchema)
    def list_files(self, state: GraphState, *, config: Optional[RunnableConfig] = None):
        """
        列出目录内容。支持递归遍历或仅顶级目录。
        参数:
        - path: (必填) 目录路径(相对当前目录)
        - recursive: (可选) 是否递归列出，默认false
        用法:
        <list_files>
        <path>src</path>
        <recursive>true</recursive>
        </list_files>
        """
        return self.wait_human_feedback()

    @tool_node(args_schema=SearchFilesSchema)
    def search_files(self, state: GraphState, *, config: Optional[RunnableConfig] = None):
        """
        正则搜索文件内容。在指定目录中递归搜索匹配正则表达式的内容，返回匹配结果及其上下文。
        **特别适用于根据symbol查询文件代码片段，如查找函数定义、类声明、变量使用等。**
        
        参数:
        - path: (必填) 搜索目录(相对当前目录)
        - regex: (必填) 正则表达式(Rust语法)
        - file_pattern: (可选) 文件过滤模式(如*.ts)
        
        **常用symbol搜索模式：**
        - 函数定义: `function\s+函数名|def\s+函数名|async\s+function\s+函数名`
        - 类定义: `class\s+类名|interface\s+接口名`
        - 变量声明: `(let|const|var)\s+变量名|变量名\s*=`
        - 方法调用: `\.方法名\(|方法名\(`
        - 导入语句: `import.*from.*模块名|require.*模块名`
        - 类型定义: `type\s+类型名|interface\s+接口名`
        
        用法:
        <search_files>
        <path>src</path>
        <regex>function\s+\w+</regex>
        <file_pattern>*.js</file_pattern>
        </search_files>
        """
        return self.wait_human_feedback()

    @tool_node(args_schema=ReplaceInFileSchema, args_parser={"diff": replace_file_search_fix})
    def replace_in_file(self, state: GraphState, *, config: Optional[RunnableConfig] = None):
        """
        精确替换文件内容，使用SEARCH/REPLACE块进行有针对性的修改。
        支持同一文件进行多个独立的修改操作。

        参数:
        - path: (必填) 文件路径(相对当前目录)
        - diff: (必填) 一个或多个SEARCH/REPLACE块
        ```
          <<<<<<< SEARCH
          要查找的精确内容
          =======
          要替换的新内容
          >>>>>>> REPLACE
        ```

        **⚠️ 极其重要的替换规则：**
        1. SEARCH内容必须与要查找的文件部分完全匹配:
           * 包括空格、缩进、换行符在内的逐字符匹配
           * 包含所有注释、文档字符串等
        2. SEARCH/REPLACE块只会替换第一个匹配项:
           * 如果需要进行多处更改，请包含多个唯一的SEARCH/REPLACE块
           * 在每个SEARCH部分中包含足够的行以唯一匹配需要更改的每组行
           * 使用多个SEARCH/REPLACE块时，按照它们在文件中出现的顺序列出
        3. 保持SEARCH/REPLACE块简洁:
           * 将大的SEARCH/REPLACE块分解成一系列较小的块，每个块只更改文件的一小部分
           * 只包含更改的行，如果需要唯一性，可以包含几个周围的行
           * 不要在SEARCH/REPLACE块中包含长段未更改的行
           * 每行必须完整。切勿在行中间截断，因为这可能导致匹配失败
        4. 特殊操作:
           * 移动代码: 使用两个SEARCH/REPLACE块(一个删除原始内容 + 一个在新位置插入)
           * 删除代码: 使用空的REPLACE部分
        5. 所有对同一个文件的修改必须放在一个replace_in_file工具调用的一个diff参数中:
           * 不要对同一个文件多次调用replace_in_file工具
           * 使用多个SEARCH/REPLACE块来表示对文件的多处修改
           * 多个SEARCH/REPLACE块必须按照它们在文件中出现的顺序列出
        6. 建议先用read_file查看原文件确切内容，确保SEARCH内容完全一致

        **多个修改块规则：**
        - 可以在一次操作中进行多个独立修改
        - 每个SEARCH/REPLACE块按文件中出现的顺序排列
        - 同一文件的所有修改建议一次完成，避免冲突

        replace_in_file工具用法示例：

        单个修改：
        <replace_in_file>
        <path>src/app.js</path>
        <diff>
        <<<<<<< SEARCH
        function greet() {
            console.log("Hello World");
        }
        =======
        function greet(name) {
            console.log(`Hello ${name}`);
        }
        >>>>>>> REPLACE
        </diff>
        </replace_in_file>

        多个修改（推荐做法）：
        <replace_in_file>
        <path>src/components/Header.tsx</path>
        <diff>
        <<<<<<< SEARCH
        import React from 'react';

        const Header = () => {
            return <h1>Welcome</h1>;
        };
        =======
        import React from 'react';

        const Header = ({ title }: { title: string }) => {
            return <h1>{title}</h1>;
        };
        >>>>>>> REPLACE

        <<<<<<< SEARCH
        export default Header;
        =======
        export default React.memo(Header);
        >>>>>>> REPLACE
        </diff>
        </replace_in_file>
        """
        return self.wait_human_feedback()

    @tool_node(args_schema=DownloadResourceSchema)
    def download_resource_to_file(self, state: GraphState, *, config: Optional[RunnableConfig] = None):
        """
        下载外部资源到本地。支持图像、音频、视频等文件，每次仅下载一个资源。
        参数:
        - resource_name: (必填) 资源名称或URL
        - resource_description: (必填) 资源描述
        - path: (必填) 保存路径(相对当前目录)
        用法:
        <download_resource_to_file>
        <resource_name>https://example.com/image.jpg</resource_name>
        <resource_description>高清山景图片</resource_description>
        <path>assets/landscape.jpg</path>
        </download_resource_to_file>
        """
        return self.wait_human_feedback()
